package cz.cvut.fit.mi_paa.sat.strategy;

import cz.cvut.fit.mi_paa.abs.Context;
import cz.cvut.fit.mi_paa.sat.SatInstance;

import java.io.FileWriter;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.Random;

/**
 * Created with IntelliJ IDEA.
 * User: milos
 * Date: 27.1.13
 * Time: 22:02
 * To change this template use File | Settings | File Templates.
 */
public class SimulatedAnnealingSatStrategy extends SatStrategy {

    private double frozenCoeficient;
    private double coolingCoeficient;
    private double initialTemperatureCoeficient;
    private int iterationThreshold;
    private int initTempType;
    private int costCalcType;
    private double costCoeficient;
    private int stateCounter;

    private Random random = new Random();
    private boolean[] best;
    private int bestWeightSum;
    private SatInstance instance;

    public SimulatedAnnealingSatStrategy(Context context) {
        super(context);
        instance = (SatInstance) context.getInstance();
        best = new boolean[instance.getVariableNum()];
    }

    @Override
    public void solve() throws IOException, CloneNotSupportedException {
        context.getSw().start();
        double actualTemp=getInitTempCalc().getInitTemp();
        double frozen = actualTemp*frozenCoeficient;
        double delta=0;
        int sol=0;
        boolean[] actualState = new boolean[instance.getVariableNum()];

        while (actualTemp >= frozen) {
            for (int i=0;i<(iterationThreshold);i++) {
                stateCounter++;
                int randomIndex = random.nextInt(instance.getVariableNum());
                delta = getDelta(actualState,randomIndex);
                if(Math.random() < Math.exp(delta/actualTemp)) {
                    if (isSolution(actualState) && isNewConfigurationBetter(actualState,best)) {
                        System.arraycopy(actualState,0,best,0,actualState.length);
                        sol = getWeightSum(best) ;
                    }
                } else {
                    actualState[randomIndex] = !actualState[randomIndex];
                }
            }
            actualTemp *= coolingCoeficient;
        }
        bestWeightSum = sol;
        context.getSw().stop();
    }

    private double getDelta(boolean[] array,int index) {
        double cost1 = getCost(array);
        array[index] = !array[index];
        double cost2 = getCost(array);
        return cost2 - cost1;
    }

    private double getCost(boolean[] array) {
        return getCostCalc().getCost(array);
    }

    private int getConfigurationWeight(boolean[] array) {
        int weight=0;
        for (int i = 0; i< array.length;i++) {
            if(array[i]) weight+=instance.getWeights()[i];
        }
        return weight;
    }

    private CostCalc getCostCalc() {
        switch (costCalcType) {
            case 1:
                return new CostCalc() {
                    @Override
                    public double getCost(boolean[] array) {
                        return instance.getWeightsSum() - getAverageWeight()*instance.getUnsatisfiableTermsCount(array)*costCoeficient;
                    }
                };
            case 2:
                return new CostCalc() {
                    @Override
                    public double getCost(boolean[] array) {
                        return instance.getWeightsSum() - ((double)getConfigurationWeight(array)/(double)instance.getVariableNum())*instance.getUnsatisfiableTermsCount(array)*costCoeficient;
                    }
                };
            case 3:
                return new CostCalc() {
                    @Override
                    public double getCost(boolean[] array) {
                        return instance.getWeightsSum() - ((double)getConfigurationWeight(array)/(double)instance.getTerms().size())*instance.getUnsatisfiableTermsCount(array)*costCoeficient;
                    }
                };
            case 4:
                return new CostCalc() {
                    @Override
                    public double getCost(boolean[] array) {
                        return ((double)instance.getSatisfiableTermsCount(array)/(double)instance.getUnsatisfiableTermsCount(array))*getAverageWeight()*costCoeficient;
                    }
                };
            case 5:
                return new CostCalc() {
                    @Override
                    public double getCost(boolean[] array) {
                        return (instance.getSatisfiableTermsCount(array)- instance.getUnsatisfiableTermsCount(array))*getAverageTermWeight()*costCoeficient;
                    }
                };
            case 6:
                return new CostCalc() {
                    @Override
                    public double getCost(boolean[] array) {
                        return instance.getSatisfiableTermsCount(array)*getAverageTermWeight()*costCoeficient;//*Math.log(0.5);
                    }
                };
            default:
                return new CostCalc() {
                    @Override
                    public double getCost(boolean[] array) {
                        return 0;
                    }
                } ;
        }
    }

    private InitTempCalc getInitTempCalc() {
        switch (initTempType) {
            case 1:
                return new InitTempCalc() {
                    @Override
                    public double getInitTemp() {
                        return instance.getWeightsSum() * initialTemperatureCoeficient;
                    }
                };
            case 2:
                return new InitTempCalc() {
                    @Override
                    public double getInitTemp() {
                        return getTermsWeightSum() * initialTemperatureCoeficient;
                    }
                };
            case 3:
                return new InitTempCalc() {
                    @Override
                    public double getInitTemp() {
                        return instance.getWeightsSum() * ((double)instance.getVariableNum()/(double)instance.getTerms().size()) * initialTemperatureCoeficient;
                    }
                };
            case 4:
                return new InitTempCalc() {
                    @Override
                    public double getInitTemp() {
                        return ((double)instance.getVariableNum()+(double)instance.getTerms().size()) * initialTemperatureCoeficient;
                    }
                };
            case 5:
                return new InitTempCalc() {
                    @Override
                    public double getInitTemp() {
                        return getAverageWeight() * ((double)instance.getVariableNum()/(double)instance.getTerms().size()) * initialTemperatureCoeficient;
                    }
                };
            default:
                return new InitTempCalc() {
                    @Override
                    public double getInitTemp() {
                        return 0;
                    }
                };
        }
    }

    interface CostCalc {
        double getCost(boolean[] array) ;
    }

    interface InitTempCalc {
        double getInitTemp() ;
    }

    @Override
    public void setArgs(String[] args) {
        frozenCoeficient = Double.valueOf(args[0]);
        coolingCoeficient = Double.valueOf(args[1]);
        initialTemperatureCoeficient = Double.valueOf(args[2]);
        iterationThreshold = Integer.valueOf(args[3]) * instance.getVariableNum();
        costCoeficient = Double.valueOf(args[4]);
        initTempType = Integer.valueOf(args[5]);
        costCalcType = Integer.valueOf(args[6]);
    }

    @Override
    public void writeResult() throws IOException {
        DecimalFormat df = new DecimalFormat("#.###");
        FileWriter resultWriter = context.getResultWriter();
        resultWriter.write(context.getSw().getTimeMillis() + " ");
        resultWriter.write(bestWeightSum + " ");
        double result = ((double)(instance.getBestWeight() - bestWeightSum))/ (double) (instance.getBestWeight());
        resultWriter.write(df.format(result)+ " ");
        resultWriter.write(stateCounter+ "\n");
    }
}
